/*
Abstract:
  IO workers. Source data provider implementation.

Last changed:
  $Id: dataprovider.cpp 82 2009-12-08 20:53:44Z vitamin.caig $

Author:
  (C) Vitamin/CAIG/2001

  This file is the part of Textator project
*/

#include "dataprovider.h"
#include "streams.h"
#include "../io/logging.h"

#include <error.h>
#include <tools.h>
#include <error_codes.h>

#include <vector>
#include <cassert>
#include <iterator>
#include <algorithm>
#include <functional>

#include <text.h>

#define FILE_TAG 18412DE0

namespace
{
  const String::value_type PATH_DELIMITERS[] = {'/', '\\', '\0'};

  class StreamHolder
  {
  public:
    explicit StreamHolder(const String& name)
     : Ptr(IO::SourceStream::Create(name).release())
    {
    }

    StreamHolder(const StreamHolder& hld) : Ptr(hld.Ptr)
    {
    }

    void Release()
    {
      delete Ptr;
      Ptr = 0;
    }

    bool operator == (const String& name) const
    {
      assert(Ptr);
      return Ptr->GetName() == name;
    }

    IO::SourceStream* operator -> () const
    {
      assert(Ptr);
      return Ptr;
    }
  private:
    IO::SourceStream* Ptr;
  };

  typedef std::vector<StreamHolder> StreamStack;
  class SourceDataProviderImpl : public IO::SourceDataProvider
  {
  public:
    SourceDataProviderImpl(const String& name) : Streams(1, StreamHolder(name))
    {
      Log::Debug("Parsing file '$1'", name);
    }

    virtual ~SourceDataProviderImpl()
    {
      std::for_each(Streams.begin(), Streams.end(), std::mem_fun_ref(&StreamStack::value_type::Release));
    }

    virtual void SetNestedSource(const String& name)
    {
      const String& normName(NormalizeName(name));
      StreamStack::const_iterator it(std::find(Streams.begin(), Streams.end(), normName));
      if (Streams.end() != it)
      {
        String pos;
        GetCurrentPosition(pos);
        throw Error(ERROR_DETAIL, ERROR_CYCLED_SOURCES,
          StringFormatter::Process(TEXT_ERROR_CYCLED_SOURCE, name, pos));
      }
      else
      {
        Streams.push_back(StreamHolder(normName));
        Log::Debug("<$1> Parsing file '$2' ('$3')", Streams.size(), name, normName);
      }
    }

    virtual bool GetLine(String& value)
    {
      while (!Streams.empty())
      {
        if (Streams.back()->GetLine(value))
        {
          return true;
        }
        Streams.back().Release();
        Streams.pop_back();
      }
      return false;//no more
    }

    virtual void GetExternalFileContent(const String& name, String& content) const
    {
      IO::SourceStream::Ptr str(IO::SourceStream::Create(NormalizeName(name)));
      String tmp;
      content.clear();
      while (str->GetLine(tmp, true))
      {
        content += tmp;
      }
    }

    virtual void GetCurrentPosition(String& pos) const
    {
      StreamStack::const_reverse_iterator it = Streams.rbegin(), lim = Streams.rend();
      if (it != lim)
      {
        (*it)->GetPosition(pos);
        ++it;
      }
      while (it != lim)
      {
        pos += TEXT_FORMAT_INCLUDEDFROM_DELIMITER;
        String npos;
        (*it)->GetPosition(npos);
        pos += npos;
        ++it;
      }
    }
  private:
    String NormalizeName(const String& name) const
    {
      const String& prevName(Streams.back()->GetName());
      String::size_type slpos(prevName.find_last_of(PATH_DELIMITERS));
      return String::npos == slpos ? name : prevName.substr(0, slpos + 1) + name;
    }
  private:
    StreamStack Streams;
  };
}

namespace IO
{
  SourceDataProvider::Ptr SourceDataProvider::Create(const String& name)
  {
    return SourceDataProvider::Ptr(new SourceDataProviderImpl(name));
  }
}
